import mjs.processing.mobile.mbt.*;
import mjs.processing.mobile.mobex.*;

/**
 * Digital Person Micro 
 *
 * This sketch allow the user the creation of a micro profile, with their 
 * name, alias and interests tags, allow the profile publication over 
 * bluetooth and discover other user with published profiles.
 * 
 * @author Mary Jane Soft - Marlon J. Manrique
 *
 * $Id: DigitalPersonMicro.pde 256 2007-06-19 19:00:07Z marlonj $
 */

// Current data version
final static String dataVersion = "0.1";

// User Profile
MicroProfile profile;

// Remote Users
RemoteUser[] remoteUsers;

// Selected user, -1 current user
int selectedUser = -1;

// Current window to show
Window currentWindow;

// Applications Windows
Menu userMenu;
Menu profileMenu;
Menu tagsMenu;
Menu remoteUserMenu;
ProfileViewer profileViewer;

// Viewer coordinates
int currentX;
int currentY;
int x;
int y;

// Bluetooth and Obex Services
int uuid = 0x1983A;
MBt mBt;
MObexServer mObexServer;
MObexClient mObexClient;

// Status Information
PFont pFont;
String message = "Ready";

// Remote user information
class RemoteUser
{
  // user screen position
  int x;
  int y;  

  // Obex service
  MService service;  

  // User profile
  MicroProfile profile;
}

/**
 * Start the application.
 */
void setup()
{
  // Load font 
  pFont = loadFont();
  textFont(pFont);

  // Create an empty profile and try to load
  profile = new MicroProfile();  
  loadProfile();

  // Go to the center of the screen
  x = currentX = width/2;
  y = currentY = height/2;  
}

/**
 * Draw 
 */
void draw()
{
  // Clear the screen
  background(0);

  // Move to the location
  translate(x,y);

  // Draw the user like a green ellipse
  fill(0x00FF00);
  stroke(0xFFFFFF);
  ellipse(0,0,10,10);
  
  // Draw the user alias
  textAlign(CENTER);    
  fill(0x00FF00);
  text(profile.alias,0,pFont.height);
 
  // If the current user is selected, draw a second ellipse
  if(selectedUser == -1)
  {
    noFill(); 
    ellipse(0,0,12,12);
  }

  // Draw status message
  textFont(pFont);
  textAlign(CENTER);  
  text(message,0,height/2 - pFont.height);

  // If any remote user
  if(remoteUsers != null)
  {
    // Set attributes
    fill(0x00FF00);
    stroke(0xFFFFFF);

    // Draw each remote user
    for(int i=0; i<length(remoteUsers); i++)
    {
      // If user is load
      if(remoteUsers[i] != null)
      {        
        // Draw a ellipse in the user location
        RemoteUser remoteUser = remoteUsers[i];
        ellipse(remoteUser.x,remoteUser.y,10,10);
        
        // Draw the profile alias
        text(remoteUser.profile.alias,
          remoteUser.x,remoteUser.y + pFont.height);
        
        // If user is selected draw another ellipse
        if(selectedUser == i)
        {
          noFill(); 
          ellipse(remoteUser.x,remoteUser.y,12,12);          
          fill(0x00FF00);
        }
      }
    }
  }

  // Draw the current window if any 
  if(currentWindow != null)
    currentWindow.draw();

  // Move the location to get the current 
  if(x > currentX) x -= 5;
  if(x < currentX) x += 5;
  if(y > currentY) y -= 5;
  if(y < currentY) y += 5;
}

/**
 * Perform user actions
 */
void keyPressed()
{  
  // Listen custom key events

  // If tags window 
  if(currentWindow == tagsMenu)
  {
    // If delete key, remove the current tag
    // keyCode -8 is delete key always ??
    if(keyCode == -8)
      deleteTag();
  }
  // If window profile close it 
  else if(currentWindow == profileViewer)
  {
    currentWindow = null;
    return;
  }      

  // Dispatch window event 
  if(currentWindow != null)
  {
    currentWindow.keyPressed();
    return;
  }  

  // Listen common key events
  switch(keyCode)
  {
    // Move the user selection around the screen
    // update the viewer position for each case 
    // and check for user selection
  case LEFT :
    if(remoteUsers != null)
    {
      selectedUser++;
      if(selectedUser == length(remoteUsers))
        selectedUser = -1;

      updateViewerPosition();
    }
    break;

  case RIGHT :
    if(remoteUsers != null)
    {
      selectedUser--;
      if(selectedUser == -2)
        selectedUser = length(remoteUsers) - 1;

      updateViewerPosition();
    }
    break;

    // Show menus according selected user
  case FIRE : 
    if(selectedUser == -1)
      showUserMenu();
    else
      showRemoteUserMenu();
    break;
  }
}

/**
 * Check the sofkey
 *
 * @param label softkey label
 */
void softkeyPressed(String label)
{
}

/**
 * Listen the events 
 */
void libraryEvent(Object library, int event, Object data)
{
  // Dispatch menu actions according menu
  if(library instanceof Menu)
  {
    switch(event)
    {
    case Menu.EVENT_ITEM_SELECTED :
      String item = (String) data;

      if(library == userMenu)
        dispatchUserMenu(item);
      else if(library == profileMenu)
        dispatchProfileMenu(item);
      else if(library == tagsMenu)
        dispatchTagsMenu(item);
      else if(library == remoteUserMenu)
        dispatchRemoteUserMenu(item);
    }    
  }
  // If bluetooth searching
  else if(library == mBt)
  {
    switch(event)
    {
      // Get all the obex services and show remote users
    case MBt.EVENT_DISCOVER_SERVICE_COMPLETED:
      MService[] mServices = (MService[]) data;
      updateRemoteUsers(mServices);
      break;
    }
  }
  // If a request is send to the obex server
  else if(library == mObexServer)
  {
    switch(event)
    {
      // Show errors
    case MObex.EVENT_ERROR :
      message = "Error" + (String) data;
      break;
      // Send the current profile to the remote user
    case MObex.EVENT_ON_GET :
      MObexObject request = (MObexObject) data;
      sendProfile(request);
      break;
    }
  }  
  // If client request is answer
  else if(library == mObexClient)
  {
    switch(event)
    {
      // Show errors
    case MObex.EVENT_ERROR :
      message = "Error" + (String) data;
      break;
      // Get the remote user profile
    case MObex.EVENT_GET_DONE :
      // Close the connection and read the response
      // Show remote user profile
      mObexClient.close();
      MObexObject response = (MObexObject) data;      
      String strProfile = new String(response.data);
      showUserProfile(strProfile);
      break;      
    }
  }  
}

/**
 * Destroy the application 
 */
void destroy()
{
  // Stop the obex server if any 
  if(mObexServer != null)
    mObexServer.stop();
}

/**
 * Show the user menu
 */
void showUserMenu()
{
  // If the menu is not created yet, create it
  if(userMenu == null)
  {
    userMenu = new Menu(this,"User");
    userMenu.add("Profile");
    userMenu.add("Publish");
    userMenu.add("Search");
    userMenu.add("Close");
    userMenu.location(-userMenu.width/2,-userMenu.height/2);    
  }

  // Set like current 
  currentWindow = userMenu;
}

/**
 * Show the profile menu
 */
void showProfileMenu()
{
  // If the menu is not created yet, create it
  if(profileMenu == null)
  {
    profileMenu = new Menu(this,"Profile");
    profileMenu.add("View");    
    profileMenu.add("Name");
    profileMenu.add("Alias");
    profileMenu.add("Tags");
    profileMenu.add("Save");
    profileMenu.add("Close");
    profileMenu.location(-profileMenu.width/2,-profileMenu.height/2);
  }

  // Set like current
  currentWindow = profileMenu;
}

/**
 * Show tag menu
 */
void showTagsMenu()
{
  // If not menu created yet, create it 
  if(tagsMenu == null) 
  {
    tagsMenu = new Menu(this,"Tags");
    tagsMenu.add("Add Tag");
    tagsMenu.add("Close");

    // Add the current profile tags to the menu
    if(profile.numTags != 0)
      for(int i=0; i<profile.numTags; i++)
        tagsMenu.add(profile.tags[i]);
  }

  // Update the menu location according size
  tagsMenu.location(- tagsMenu.width/2,-tagsMenu.height/2);

  // Set like current
  currentWindow = tagsMenu;
}

/**
 * Dispatch menu user action
 *
 * @param item Selected item
 */
void dispatchUserMenu(String item)
{
  if(item.equals("Close"))
    currentWindow = null;
  else if(item.equals("Profile"))
    showProfileMenu();
  else if(item.equals("Publish"))
    startService();    
  else if(item.equals("Search"))
    searchService();
}

/**
 * Dispatch menu user action
 *
 * @param item Selected item
 */
void dispatchProfileMenu(String item)
{
  if(item.equals("Close"))
    currentWindow = userMenu;
  else if(item.equals("View"))
    showProfile(profile,0,0);
  else if(item.equals("Name"))
    profile.name = textInput("Name",profile.name,100);
  else if(item.equals("Alias"))
    profile.alias = textInput("Alias",profile.alias,100);
  else if(item.equals("Tags"))
    showTagsMenu();  
  else if(item.equals("Save"))
    saveProfile();  
}

/**
 * Dispatch menu user action
 *
 * @param item Selected item
 */
void dispatchTagsMenu(String item)
{
  if(item.equals("Close"))
    currentWindow = profileMenu;
  else if(item.equals("Add Tag"))
  {
    // Adds a new tag to the profile and menu
    String tag = textInput("Tag","",30);
    profile.addTag(tag);
    tagsMenu.add(tag);
  }
}

/**
 * Dispatch menu user action
 *
 * @param item Selected item
 */
void dispatchRemoteUserMenu(String item)
{
  if(item.equals("Close"))
    currentWindow = null;
  else if(item.equals("View Profile"))
    showRemoteUserProfile();
}

/**
 * Delete the current selected tag in the tags menu
 */
void deleteTag()
{
  // if any tag is selected
  if(tagsMenu.selectedItem > 1)
  {
    // Remove tag form menu and profile
    tagsMenu.deleteItem(tagsMenu.selectedItem);
    profile.deleteTag(tagsMenu.selectedItem);

    // Update selection
    tagsMenu.selectedItem--;
  }
}

/**
 * Saves the profile in the phone memory 
 */
void saveProfile()
{
  // Create and array to storage profile values
  String[] lines = new String[profile.numTags + 3];

  // Current format :
  // version, name, alias, tags 

  // Set values 
  lines[0] = "0.1";
  lines[1] = profile.name;
  lines[2] = profile.alias;

  // Copy tags values
  for(int i=0; i<profile.numTags; i++)
    lines[i+3] = profile.tags[i];

  // Save profile into file
  saveStrings("profile.dat",lines);
}

/**
 * Loads the profile from the phone memory
 */
void loadProfile()
{
  // Grab the values
  String[] lines = loadStrings("profile.dat");

  // If an empty array, show a message
  if(length(lines) == 0)
  {
    // Empty profile alias
    profile.alias = "";    
    message = "Profile not found";
    return;
  }

  // If the profile has another version 
  // show a message
  if(!lines[0].equals(dataVersion))
  {
    message = "Data version " + lines[0] + " not supported";
    return;
  }

  // Get values
  profile.name = lines[1];
  profile.alias = lines[2];

  // Add found tags to the current profile
  int numTags = length(lines) - 3;    
  for(int i=0; i<numTags; i++)
    profile.addTag(lines[i+3]);
}

/**
 * Show the given profile in the location specified
 *
 * @param profile Profile to show
 * @param x coordinate x
 * @param y coordinate y
 */
void showProfile(MicroProfile profile, int x, int y)
{
  // Create the profile viewer if is not created yet
  if(profileViewer == null)
  {
    profileViewer = new ProfileViewer();
    profileViewer.size(width/2,height/2);
  }

  // Update the location and profile
  profileViewer.location(x,y);
  profileViewer.profile(profile);

  // Ste like current
  currentWindow = profileViewer;
}

/**
 * Start the Obex service to publish the profile
 */
void startService()
{
  // If no server created, create one 
  if(mObexServer == null)
  {
    mObexServer = new MObexServer(this,uuid);
    mObexServer.start();
  }
}

/**
 * Search users with published profile
 */
void searchService()
{
  // If bluetooth service is not created, create it
  if(mBt == null)
    mBt = new MBt(this);

  // Start discovering services with the uuid
  mBt.discoverServices(uuid);
}

/**
 * Update remote users array with the new discovered ones
 *
 * @param mServices obex services discovered
 */
void updateRemoteUsers(MService[] mServices)
{
  // Create a new array 
  remoteUsers = new RemoteUser[length(mServices)];

  // Create a new user with each service
  for(int i=0; i<length(mServices); i++)
  {
    // Create the remote user
    RemoteUser remoteUser = new RemoteUser();    
    
    // Set alias like the devicename
    remoteUser.profile = new MicroProfile();
    remoteUser.profile.alias = mServices[i].device.name;
    
    // Set remote service
    remoteUser.service = mServices[i];    

    // Move to a random position
    remoteUser.x = random(-width/2,width/2);
    remoteUser.y = random(-height/2,height/2);    

    // Add to the array
    remoteUsers[i] = remoteUser;
  }
}

/**
 * Show the remote user menu
 */
void showRemoteUserMenu()
{
  // If menu is not created yet, create it 
  if(remoteUserMenu == null)
  {
    remoteUserMenu = new Menu(this,"Remote User");
    remoteUserMenu.add("View Profile");
    remoteUserMenu.add("Close");
  }

  // Get user location
  int tmpX = remoteUsers[selectedUser].x;
  int tmpY = remoteUsers[selectedUser].y;

  // Move the window to the user location
  remoteUserMenu.location(tmpX-remoteUserMenu.width/2,tmpY-remoteUserMenu.height/2);  

  // Set like current
  currentWindow = remoteUserMenu;
}

/**
 * Move the viewer position to a the location of the selected user
 */
void updateViewerPosition()
{
  // Center of the screen by default 
  // The current user is at the center
  int tmpX = width/2;
  int tmpY = height/2;

  // If a remote user is selected 
  // move to that position
  if(selectedUser != -1) 
  {
    tmpX -= remoteUsers[selectedUser].x;
    tmpY -= remoteUsers[selectedUser].y;
  }

  // Update current location
  currentX = tmpX;
  currentY = tmpY;  
}

/**
 * Request the remote user profile
 */
void loadRemoteProfile()
{
  // Get the selected user
  RemoteUser remoteUser = remoteUsers[selectedUser];

  // Create a obex client and request the profile
  mObexClient = new MObexClient(this,remoteUser.service);
  mObexClient.get("profile.dat"); 
}

/**
 * Sends the profile to the remote user
 *
 * @param request Obex user request
 */
void sendProfile(MObexObject request)
{
  // Load the profile
  String[] lines = loadStrings("profile.dat");

  // Create a string with the profile values separate by |
  // dataVersion|name|alias|tag1|tag2 ...
  String string = join(lines,"|");

  // Convert string in data array
  byte[] data = string.getBytes();

  // Set the data in the request and send to the remote user
  request.data = data;
  request.send();
}

/**
 * Show the user profile send by the remote user
 *
 * @param strProfile String version of the profile
 */
void showUserProfile(String strProfile)
{
  // Create a new profile
  MicroProfile userProfile = new MicroProfile();

  // Separate the profile components
  String[] lines = split(strProfile,"|");

  // Set values
  userProfile.name = lines[1];
  userProfile.alias = lines[2];

  // Add tags to the profile
  int numTags = length(lines) - 3;    
  for(int i=0; i<numTags; i++)
    userProfile.addTag(lines[i+3]);

  // Set profile remote user profile
  RemoteUser remoteUser = remoteUsers[selectedUser];
  remoteUser.profile = userProfile;

  // Show the profile in the remote user location
  showProfile(userProfile,remoteUser.x,remoteUser.y);
}

/**
 * Shows the remote user profile.
 */
void showRemoteUserProfile()
{
  // Get the current remote user
  RemoteUser remoteUser = remoteUsers[selectedUser];
  
  // If no profile present, load it 
  if(remoteUser.profile.name == null)
    loadRemoteProfile();
  // Show the current profile
  else
    showProfile(remoteUser.profile,remoteUser.x,remoteUser.y);
}
